<!DOCTYPE html>
<html lang="en">

<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0, shrink-to-fit=no">
	<title>cardboard-gaze</title>
	<style>
		* {
			margin: 0;
			padding: 0;
		}

		html,body {
			height: 100%;
		}

		body {
			font-size: 14px;
			font-family: "Arial", "Microsoft YaHei", "黑体", sans-serif;
			overflow: hidden;
		}

		html,body,.main-page {
			position: relative;
			height: 100%;
			overflow: hidden;
		}

		.vr-btn {
			position: fixed;
			right: 18px;
			bottom: 18px;
			padding: 8px 12px;
			background-color: #00aadd;
			text-align: center;
			color: #fff;
			font-size: 14px;
			cursor: pointer;
			z-index: 100;
		}
	</style>
</head>
<body>
	<section class="main-page">
		<div class="vr-btn">进入VR</div>
	</section>
</body>
<script src="./vendor/webvr-polyfill.js"></script>
<script src="./vendor/three.min.js"></script>
<script src="./vendor/tween.min.js"></script>
<script>
	/**
** author:YoneChen
** date:2017-08-18
**/
	class WebVRApp {
		constructor() {
			// 初始化场景
			this.scene = new THREE.Scene();
			// 初始化相机
			this.camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
			this.scene.add(this.camera);

			// 初始化渲染器
			this.renderer = new THREE.WebGLRenderer({ antialias: true });
			this.renderer.setSize(window.innerWidth, window.innerHeight);
			this.renderer.setClearColor(0x519EcB);
			this.renderer.shadowMapEnabled = true;
			this.renderer.setPixelRatio(window.devicePixelRatio);
			document.querySelector('.main-page').appendChild(this.renderer.domElement);

			this.clock = new THREE.Clock();
			// VR初始化
			this.initVR();
			// 往场景添加3d物体
			this.start();
			this.bindEvent();
			// 渲染动画
			this.renderer.animate(this.update.bind(this));
		}
		bindEvent() {
			// 窗口大小调整监听
			window.addEventListener('resize', this.resize.bind(this), false);
		}
		start() {
			const { scene, camera } = this;
			// 创建准心
			camera.add(this.createCrosshair());
			// 创建光线
			scene.add(new THREE.AmbientLight(0xFFFFFF));
			scene.add(this.createLight());
			// 创建地面
			scene.add(this.createGround(1000, 1000));
			this.gazer = new Gazer();

			// 创建立方体
			for (let i = 0; i < 100; i++) {
				const cube = this.createCube(2, 2, 2);
				cube.position.set(
					100 * Math.random() - 50,
					50 * Math.random() - 10,
					100 * Math.random() - 50
				);
				scene.add(cube);

				this.gazer.on(cube, 'gazeEnter', () => {
					cube.material.opacity = 0.5;
				});
				this.gazer.on(cube, 'gazeLeave', () => {
					cube.material.opacity = 1;
				});
			}
		}
		initVR() {
			const { renderer } = this;
			renderer.vr.enabled = true;
			// 获取VRDisplay实例
			navigator.getVRDisplays().then(display => {
				// 将display实例传给renderer渲染器
				renderer.vr.setDevice(display[0]);
				const button = document.querySelector('.vr-btn');
				VRButton.init(display[0], renderer, button, () => button.textContent = '退出VR', () => button.textContent = '进入VR');
			}).catch(err => console.warn(err));
		}
		resize() {
			const { camera, renderer } = this;
			// 窗口调整重新调整渲染器
			camera.aspect = window.innerWidth / window.innerHeight;
			camera.updateProjectionMatrix();
			renderer.setSize(window.innerWidth, window.innerHeight);
		}
		createCrosshair() {
			// 创建准心
			const geometry = new THREE.CircleGeometry(0.002, 16);
			const material = new THREE.MeshBasicMaterial({
				color: 0xffffff,
				opacity: 0.5,
				transparent: true
			});
			const crosshair = new THREE.Mesh(geometry, material);
			crosshair.position.z = -0.5;
			return crosshair;
		}
		createCube(width = 2, height = 2, depth = 2, color = 0xef6500) {
			// 创建立方体

			const geometry = new THREE.CubeGeometry(width, height, depth);
			const material = new THREE.MeshLambertMaterial({
				color: color,
				needsUpdate: true,
				opacity: 1,
				transparent: true
			});
			const cube = new THREE.Mesh(geometry, material);
			cube.castShadow = true;
			return cube;
		}
		createLight() {
			// 创建光线
			const light = new THREE.DirectionalLight(0xffffff, 0.3);
			light.position.set(50, 50, -50);
			light.castShadow = true;
			light.shadow.mapSize.width = 2048;
			light.shadow.mapSize.height = 512;
			return light;
		}
		createGround(width, height) {
			// 创建地平面
			const geometry = new THREE.PlaneBufferGeometry(width, height);
			const material = new THREE.MeshPhongMaterial({ color: 0xaaaaaa });
			const ground = new THREE.Mesh(geometry, material);
			ground.rotation.x = - Math.PI / 2;
			ground.position.y = -10;
			ground.receiveShadow = true;
			return ground;
		}
		update() {
			const { scene, camera, renderer, clock } = this;
			const delta = clock.getDelta() * 60;
			// 启动渲染
			this.gazer.update(camera);
			renderer.render(scene, camera);
		}
	}
	// VR按钮控制
	const VRButton = {
		/** 
		 * @param {VRDisplay} display VRDisplay实例
		 * @param {THREE.WebGLRenderer} renderer 渲染器
		 * @param {HTMLElement} button VR控制按钮
		 * @param {Function} enterVR 点击进入VR模式时回调
		 * @param {Function} exitVR 点击退出VR模式时回调
		 **/
		init(display, renderer, button, enterVR, exitVR) {
			if (display) {
				button.addEventListener('click', e => {
					display.isPresenting ? display.exitPresent() : display.requestPresent([{ source: renderer.domElement }]);
				});
				window.addEventListener('vrdisplaypresentchange', e => {
					display.isPresenting ? enterVR() : exitVR();
				}, false);
			} else {
				button.remove();
			}
		}

	}
	// 凝视监听器
	class Gazer {
		constructor() {
			// 初始化射线发射源
			this.raycaster = new THREE.Raycaster();
			this._center = new THREE.Vector2();
			this.rayList = {}, this.targetList = [];
			this._lastTarget = null;
		}
		get target() {
			return this._lastTarget;
		}
		/** 
		 * @param {THREE.Mesh} target 监听的3d网格
		 * @param {String} eventType 事件类型 
		 *      'gazeEnter': '射线击中物体触发一次', 
		 *      'gazeTrigger': '射线击中物体触发', 
		 *      'gazeLeave': '射线离开物体触发'
		 * @param {Function} callback 事件回调
		 **/
		on(target, eventType, callback) {
			const noop = () => { };
			if (!this.rayList[target.id]) this.rayList[target.id] = {
				target,
				gazeEnter: noop,
				gazeTrigger: noop,
				gazeLeave: noop
			};
			this.rayList[target.id][eventType] = callback;
			this.targetList = Object.keys(this.rayList).map(key => this.rayList[key].target);
		}
		off(target, eventType) {
			if (!eventType) {
				delete this.rayList[target.id];
				this.targetList = Object.keys(this.rayList).map(key => this.rayList[key].target);
			} else {
				const noop = () => { };
				this.rayList[target.id][eventType] = noop;
			}
		}
		clear() {
			this.rayList = {}, this.targetList = [];
		}
		update(camera) {
			if (!this.targetList.length) return;
			//更新射线位置
			this.raycaster.setFromCamera(this._center, camera);
			const intersects = this.raycaster.intersectObjects(this.targetList);

			if (intersects.length > 0) { // 当前帧射线击中物体
				const currentTarget = intersects[0].object;
				if (this._lastTarget) { // 上一帧射线击中物体
					if (this._lastTarget.id !== currentTarget.id) {
						// 上一帧射线击中物体与当前帧不同，触发上一帧物体的gazeLeave事件，触发当前帧物体的gazeEnter事件
						this.rayList[this._lastTarget.id].gazeLeave();
						this.rayList[currentTarget.id].gazeEnter();
					}
				} else { // 上一帧射线未击中物体
					this.rayList[currentTarget.id].gazeEnter(); // 上一帧射线没有击中物体，触发当前帧物体的gazeEnter事件
				}
				this.rayList[currentTarget.id].gazeTrigger(); // 当前帧射线击中物体，触发物体的gazeTrigger事件
				this._lastTarget = currentTarget;
			} else { // 当前帧我击中物体
				if (this._lastTarget) this.rayList[this._lastTarget.id].gazeLeave(); // 上一帧射线击中物体，触发上一帧物体gazeLeave
				this._lastTarget = null;
			}
		}
	}
	new WebVRApp();
</script>
</html>